/*-------------------------------------------------------------------------
 *
 * execIndexing.c
 *	  插入索引元组并强制唯一和排除约束的例程。
 *
 * ExecInsertIndexTuples()是主要入口点。它在将元组插入堆之后被调用，并将相应的索引元组插入所有索引。同时，它强制任何唯一和排除约束：
 *
 * 唯一索引
 * --------------
 *
 * 强制唯一约束是简单明了的。当索引AM将元组插入索引时，它也会检查索引中是否已经存在冲突的元组。它以原子方式进行，所以即使两个后端试图同时插入相同的键，只有一个会成功。确保原子性的所有逻辑、等待未完成事务的完成都由索引AM处理。
 *
 * 如果唯一约束被推迟，我们请求索引AM在发现冲突时不要抛出错误。相反，我们记下发生了冲突，并返回有冲突的索引列表给调用者。调用者必须稍后通过调用index_insert()并使用UNIQUE_CHECK_EXISTING选项来重新检查它们。
 *
 * 排除约束
 * ---------------------
 *
 * 排除约束与唯一索引不同，因为在将元组插入索引时，索引AM并不同时检查重复键。插入后，我们在索引上执行单独的扫描，以检查是否有冲突的元组，如果发现一个，我们会抛出错误并终止该事务。如果冲突元组的插入者或删除者正在进行中，我们会等待其先完成。
 *
 * 如果两个后端同时插入元组，并且都尝试等待彼此，将会发生死锁。因此选项2不可接受。选项1避免了死锁，但更可能出现活锁。两个事务会在另一个事务回退时立即醒来。然后它们会都重试，并再次相互冲突，反复出现这种情况。
 *
 * 为了避免活锁，其中一个后端必须先回退，然后等待，而另一个则在没有回退的情况下等待。谁先回退并不重要，因此我们采用一个任意规则，即具有更高XID的事务先回退。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/executor/execIndexing.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/genam.h"
#include "access/relscan.h"
#include "access/tableam.h"
#include "access/xact.h"
#include "catalog/index.h"
#include "executor/executor.h"
#include "nodes/nodeFuncs.h"
#include "storage/lmgr.h"
#include "utils/snapmgr.h"

/* check_exclusion_or_unique_constraint() 的 waitMode 参数 */
typedef enum
{
	CEOUC_WAIT,
	CEOUC_NOWAIT,
	CEOUC_LIVELOCK_PREVENTING_WAIT
} CEOUC_WAIT_MODE;

static bool fc_check_exclusion_or_unique_constraint(Relation fc_heap, Relation fc_index,
												 IndexInfo *fc_indexInfo,
												 ItemPointer fc_tupleid,
												 Datum *fc_values, bool *fc_isnull,
												 EState *fc_estate, bool fc_newIndex,
												 CEOUC_WAIT_MODE fc_waitMode,
												 bool fc_errorOK,
												 ItemPointer fc_conflictTid);

static bool fc_index_recheck_constraint(Relation fc_index, Oid *fc_constr_procs,
									 Datum *fc_existing_values, bool *fc_existing_isnull,
									 Datum *fc_new_values);
static bool fc_index_unchanged_by_update(ResultRelInfo *fc_resultRelInfo,
									  EState *fc_estate, IndexInfo *fc_indexInfo,
									  Relation fc_indexRelation);
static bool fc_index_expression_changed_walker(Node *fc_node,
											Bitmapset *fc_allUpdatedCols);

/* ----------------------------------------------------------------
 *		ExecOpenIndices
 *
 *		查找与结果关系相关的索引，打开它们，
 *		并将有关它们的信息保存在结果 ResultRelInfo 中。
 *
 *		进入时，调用者已经打开并锁定
 *		resultRelInfo->ri_RelationDesc。
 * ----------------------------------------------------------------
 */
void ExecOpenIndices(ResultRelInfo *fc_resultRelInfo, bool fc_speculative)
{
	Relation	fc_resultRelation = fc_resultRelInfo->ri_RelationDesc;
	List	   *fc_indexoidlist;
	ListCell   *fc_l;
	int			fc_len,
				fc_i;
	RelationPtr fc_relationDescs;
	IndexInfo **fc_indexInfoArray;

	fc_resultRelInfo->ri_NumIndices = 0;

	/* 当没有索引时的快速路径 */
	if (!RelationGetForm(fc_resultRelation)->relhasindex)
		return;

	/*
	 * 获取缓存的索引 OID 列表
	 */
	fc_indexoidlist = RelationGetIndexList(fc_resultRelation);
	fc_len = list_length(fc_indexoidlist);
	if (fc_len == 0)
		return;

	/*
	 * 为结果数组分配空间
	 */
	fc_relationDescs = (RelationPtr) palloc(fc_len * sizeof(Relation));
	fc_indexInfoArray = (IndexInfo **) palloc(fc_len * sizeof(IndexInfo *));

	fc_resultRelInfo->ri_NumIndices = fc_len;
	fc_resultRelInfo->ri_IndexRelationDescs = fc_relationDescs;
	fc_resultRelInfo->ri_IndexRelationInfo = fc_indexInfoArray;

	/*
	 * 对于每个索引，打开索引关系并保存 pg_index 信息。我们
	 * 获取 RowExclusiveLock，表示我们将更新索引。
	 *
	 * 注意：即使索引未准备就绪，我们也这样做；优化
	 * 未准备就绪的情况不值得麻烦。
	 */
	fc_i = 0;
	foreach(fc_l, fc_indexoidlist)
	{
		Oid			fc_indexOid = lfirst_oid(fc_l);
		Relation	fc_indexDesc;
		IndexInfo  *fc_ii;

		fc_indexDesc = index_open(fc_indexOid, RowExclusiveLock);

		/* 从索引的 pg_index 信息中提取索引键信息 */
		fc_ii = BuildIndexInfo(fc_indexDesc);

		/*
		 * 如果索引要用于推测插入，则添加独特索引条目所需的额外信息。
		 */
		if (fc_speculative && fc_ii->ii_Unique)
			BuildSpeculativeIndexInfo(fc_indexDesc, fc_ii);

		fc_relationDescs[fc_i] = fc_indexDesc;
		fc_indexInfoArray[fc_i] = fc_ii;
		fc_i++;
	}

	list_free(fc_indexoidlist);
}

/* ----------------------------------------------------------------
 *		ExecCloseIndices
 *
 *		关闭存储在 resultRelInfo 中的索引关系
 * ----------------------------------------------------------------
 */
void ExecCloseIndices(ResultRelInfo *fc_resultRelInfo)
{
	int			fc_i;
	int			fc_numIndices;
	RelationPtr fc_indexDescs;

	fc_numIndices = fc_resultRelInfo->ri_NumIndices;
	fc_indexDescs = fc_resultRelInfo->ri_IndexRelationDescs;

	for (fc_i = 0; fc_i < fc_numIndices; fc_i++)
	{
		if (fc_indexDescs[fc_i] == NULL)
			continue;			/* 不应该发生？ */

		/* 获取 ExecOpenIndices 获得的锁 */
		index_close(fc_indexDescs[fc_i], RowExclusiveLock);
	}

	/*
	 * XXX 是否应该在这里释放 indexInfo 数组？ 目前我们假设这样的内容
	 * 会在 FreeExecutorState 中自动清理。
	 */
}

/* ----------------------------------------------------------------
 *		ExecInsertIndexTuples
 *
 *		该例程负责将索引元组插入到所有索引结果关系的关系中
 *		当堆元组被插入到结果关系中时。
 *
 *		当 'update' 为 true 时，执行器正在执行一个 UPDATE
 *		该操作不能使用像 heapam 的 HOT 这样的优化
 *		（更一般地说，调用了 table_tuple_update() 并将
 *		'update_indexes' 设置为 true）。 收到这个提示使我们考虑
 *		是否应该传递 'indexUnchanged' 提示。 这是我们为每个
 *		index_insert() 调用确定的，如果 'update' 为 true。
 *		（当 'update' 为 false 时，我们已经知道不应该将
 *		提示传递给任何索引。）
 *
 *		唯一性和排除约束同时被执行。 这返回一个索引 OID 列表，用于
 *		任何唯一性或排除约束，这些约束是延迟的，并且有潜在的
 *		（未确认的）冲突。 （如果 noDupErr == true，非延迟约束
 *		也会这样做，但会向调用者报告冲突是投机的还是延迟的冲突）
 *
 *		如果 'arbiterIndexes' 非空，则 noDupErr 仅适用于
 *		这些索引。 NIL 表示 noDupErr 适用于所有索引。
 * ----------------------------------------------------------------
 */
List * ExecInsertIndexTuples(ResultRelInfo *fc_resultRelInfo,
					  TupleTableSlot *fc_slot,
					  EState *fc_estate,
					  bool fc_update,
					  bool fc_noDupErr,
					  bool *fc_specConflict,
					  List *fc_arbiterIndexes)
{
	ItemPointer fc_tupleid = &fc_slot->tts_tid;
	List	   *fc_result = NIL;
	int			fc_i;
	int			fc_numIndices;
	RelationPtr fc_relationDescs;
	Relation	fc_heapRelation;
	IndexInfo **fc_indexInfoArray;
	ExprContext *fc_econtext;
	Datum		fc_values[INDEX_MAX_KEYS];
	bool		fc_isnull[INDEX_MAX_KEYS];

	Assert(ItemPointerIsValid(fc_tupleid));

	/*
	 * 从结果关系信息结构中获取信息。
	 */
	fc_numIndices = fc_resultRelInfo->ri_NumIndices;
	fc_relationDescs = fc_resultRelInfo->ri_IndexRelationDescs;
	fc_indexInfoArray = fc_resultRelInfo->ri_IndexRelationInfo;
	fc_heapRelation = fc_resultRelInfo->ri_RelationDesc;

	/* 完整性检查：插槽必须属于与 resultRelInfo 相同的关系。 */
	Assert(fc_slot->tts_tableOid == RelationGetRelid(fc_heapRelation));

	/*
	 * 我们将使用 EState 的每元组上下文来评估谓词
	 * 和索引表达式（如果尚不存在，则创建它）。
	 */
	fc_econtext = GetPerTupleExprContext(fc_estate);

	/* 安排econtext的扫描元组为待测试的元组 */
	fc_econtext->ecxt_scantuple = fc_slot;

	/*
	 * 对于每个索引，形成并插入索引元组
	 */
	for (fc_i = 0; fc_i < fc_numIndices; fc_i++)
	{
		Relation	fc_indexRelation = fc_relationDescs[fc_i];
		IndexInfo  *fc_indexInfo;
		bool		fc_applyNoDupErr;
		IndexUniqueCheck fc_checkUnique;
		bool		fc_indexUnchanged;
		bool		fc_satisfiesConstraint;

		if (fc_indexRelation == NULL)
			continue;

		fc_indexInfo = fc_indexInfoArray[fc_i];

		/* 如果索引标记为只读，则忽略它 */
		if (!fc_indexInfo->ii_ReadyForInserts)
			continue;

		/* 检查部分索引 */
		if (fc_indexInfo->ii_Predicate != NIL)
		{
			ExprState  *fc_predicate;

			/*
			 * 如果谓词状态尚未设置，请创建它（在 estate 的
			 * 每查询上下文中）
			 */
			fc_predicate = fc_indexInfo->ii_PredicateState;
			if (fc_predicate == NULL)
			{
				fc_predicate = ExecPrepareQual(fc_indexInfo->ii_Predicate, fc_estate);
				fc_indexInfo->ii_PredicateState = fc_predicate;
			}

			/* 如果谓词不满足则跳过此索引更新 */
			if (!ExecQual(fc_predicate, fc_econtext))
				continue;
		}

		/*
		 * FormIndexDatum 用适当的值填充索引列的值和 isnull 参数.
		 */
		FormIndexDatum(fc_indexInfo,
					   fc_slot,
					   fc_estate,
					   fc_values,
					   fc_isnull);

		/* 检查是否将 noDupErr 应用到此索引 */
		fc_applyNoDupErr = fc_noDupErr &&
			(fc_arbiterIndexes == NIL ||
			 list_member_oid(fc_arbiterIndexes,
							 fc_indexRelation->rd_index->indexrelid));

		/*
		 * 索引 AM 执行实际插入，并进行唯一性检查。
		 *
		 * 对于即时模式的唯一索引，我们只是告诉索引 AM
		 * 如果不唯一就抛出错误。
		 *
		 * 对于可延迟的唯一索引，我们告诉索引 AM 仅检测
		 * 可能的非唯一性，如果需要进一步检查，我们将该索引 OID
		 * 添加到结果列表中。
		 *
		 * 对于投机插入（由 INSERT ... ON CONFLICT 使用），请
		 * 与可延迟的唯一索引相同。
		 */
		if (!fc_indexRelation->rd_index->indisunique)
			fc_checkUnique = UNIQUE_CHECK_NO;
		else if (fc_applyNoDupErr)
			fc_checkUnique = UNIQUE_CHECK_PARTIAL;
		else if (fc_indexRelation->rd_index->indimmediate)
			fc_checkUnique = UNIQUE_CHECK_YES;
		else
			fc_checkUnique = UNIQUE_CHECK_PARTIAL;

		/*
		 * 此索引肯定会有一个 index_insert() 调用。如果我们
		 * 作为 UPDATE 语句的一部分被调用，请考虑是否应该传递
		 * 'indexUnchanged' = true 提示。
		 */
		fc_indexUnchanged = fc_update && fc_index_unchanged_by_update(fc_resultRelInfo,
															 fc_estate,
															 fc_indexInfo,
															 fc_indexRelation);

		fc_satisfiesConstraint =
			index_insert(fc_indexRelation, /* 索引关系 */
						 fc_values,	/* 索引 Datums 数组 */
						 fc_isnull,	/* 空标志 */
						 fc_tupleid,	/* 堆元组的 tid */
						 fc_heapRelation,	/* 堆关系 */
						 fc_checkUnique,	/* 要进行的唯一性检查类型 */
						 fc_indexUnchanged,	/* UPDATE 时没有逻辑变化？ */
						 fc_indexInfo);	/* 索引 AM 可能需要这个 */

		/*
		 * 如果索引有一个关联的排除约束，请检查它。
		 * 这比唯一性检查的过程简单，因为我们总是先插入
		 * 然后再检查。如果约束是延迟的，我们无论如何都会检查，
		 * 但不会因为违反而抛出错误，也不会等待来自并发插入的
		 * 确定结果；而是我们将排队一个重新检查事件。类似地，
		 * noDupErr 调用者（投机插入者）将在稍后重新检查，并
		 * 等待确定的结果。
		 *
		 * 排除约束的索引不能也为 UNIQUE（不是一个
		 * 重要的属性，我们只是不会在文法中允许它），因此
		 * 不需要保留 satisfiesConstraint 的先前状态。
		 */
		if (fc_indexInfo->ii_ExclusionOps != NULL)
		{
			bool		fc_violationOK;
			CEOUC_WAIT_MODE fc_waitMode;

			if (fc_applyNoDupErr)
			{
				fc_violationOK = true;
				fc_waitMode = CEOUC_LIVELOCK_PREVENTING_WAIT;
			}
			else if (!fc_indexRelation->rd_index->indimmediate)
			{
				fc_violationOK = true;
				fc_waitMode = CEOUC_NOWAIT;
			}
			else
			{
				fc_violationOK = false;
				fc_waitMode = CEOUC_WAIT;
			}

			fc_satisfiesConstraint =
				fc_check_exclusion_or_unique_constraint(fc_heapRelation,
													 fc_indexRelation, fc_indexInfo,
													 fc_tupleid, fc_values, fc_isnull,
													 fc_estate, false,
													 fc_waitMode, fc_violationOK, NULL);
		}

		if ((fc_checkUnique == UNIQUE_CHECK_PARTIAL ||
			 fc_indexInfo->ii_ExclusionOps != NULL) &&
			!fc_satisfiesConstraint)
		{
			/*
			 * 元组可能违反唯一性或排除约束，因此请记下
			 * 索引，以便我们可以稍后重新检查它。
			 * 投机插入者被告知是否存在投机冲突，因为
			 * 这总是需要重启。
			 */
			fc_result = lappend_oid(fc_result, RelationGetRelid(fc_indexRelation));
			if (fc_indexRelation->rd_index->indimmediate && fc_specConflict)
				*fc_specConflict = true;
		}
	}

	return fc_result;
}



/* ----------------------------------------------------------------
 *		ExecCheckIndexConstraints
 *
 *		此例程检查一个元组是否违反任何唯一或
 *		排除约束。如果没有冲突则返回true。
 *		否则返回false，冲突元组的TID将返回在*conflictTid中。
 *
 *		如果给出了'arbiterIndexes'，则仅检查这些索引。
 *		NIL表示所有索引。
 *
 *		请注意，这不会以任何方式锁定值，因此
 *		可能在此返回后立即插入一个冲突元组。
 *		但这可以作为插入前的检查。
 * ----------------------------------------------------------------
 */
bool ExecCheckIndexConstraints(ResultRelInfo *fc_resultRelInfo, TupleTableSlot *fc_slot,
						  EState *fc_estate, ItemPointer fc_conflictTid,
						  List *fc_arbiterIndexes)
{
	int			fc_i;
	int			fc_numIndices;
	RelationPtr fc_relationDescs;
	Relation	fc_heapRelation;
	IndexInfo **fc_indexInfoArray;
	ExprContext *fc_econtext;
	Datum		fc_values[INDEX_MAX_KEYS];
	bool		fc_isnull[INDEX_MAX_KEYS];
	ItemPointerData fc_invalidItemPtr;
	bool		fc_checkedIndex = false;

	ItemPointerSetInvalid(fc_conflictTid);
	ItemPointerSetInvalid(&fc_invalidItemPtr);

	/*
	 * 从结果关系信息结构中获取信息。
	 */
	fc_numIndices = fc_resultRelInfo->ri_NumIndices;
	fc_relationDescs = fc_resultRelInfo->ri_IndexRelationDescs;
	fc_indexInfoArray = fc_resultRelInfo->ri_IndexRelationInfo;
	fc_heapRelation = fc_resultRelInfo->ri_RelationDesc;

	/*
	 * 我们将使用 EState 的每元组上下文来评估谓词
	 * 和索引表达式（如果尚不存在，则创建它）。
	 */
	fc_econtext = GetPerTupleExprContext(fc_estate);

	/* 安排econtext的扫描元组为待测试的元组 */
	fc_econtext->ecxt_scantuple = fc_slot;

	/*
	 * 对于每个索引，形成索引元组并检查其是否满足约束。
	 */
	for (fc_i = 0; fc_i < fc_numIndices; fc_i++)
	{
		Relation	fc_indexRelation = fc_relationDescs[fc_i];
		IndexInfo  *fc_indexInfo;
		bool		fc_satisfiesConstraint;

		if (fc_indexRelation == NULL)
			continue;

		fc_indexInfo = fc_indexInfoArray[fc_i];

		if (!fc_indexInfo->ii_Unique && !fc_indexInfo->ii_ExclusionOps)
			continue;

		/* 如果索引标记为只读，则忽略它 */
		if (!fc_indexInfo->ii_ReadyForInserts)
			continue;

		/* 当请求特定的仲裁索引时，仅检查它们 */
		if (fc_arbiterIndexes != NIL &&
			!list_member_oid(fc_arbiterIndexes,
							 fc_indexRelation->rd_index->indexrelid))
			continue;

		if (!fc_indexRelation->rd_index->indimmediate)
			ereport(ERROR,
					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
					 errmsg("ON CONFLICT does not support deferrable unique constraints/exclusion constraints as arbiters"),
					 errtableconstraint(fc_heapRelation,
										RelationGetRelationName(fc_indexRelation))));

		fc_checkedIndex = true;

		/* 检查部分索引 */
		if (fc_indexInfo->ii_Predicate != NIL)
		{
			ExprState  *fc_predicate;

			/*
			 * 如果谓词状态尚未设置，请创建它（在 estate 的
			 * 每查询上下文中）
			 */
			fc_predicate = fc_indexInfo->ii_PredicateState;
			if (fc_predicate == NULL)
			{
				fc_predicate = ExecPrepareQual(fc_indexInfo->ii_Predicate, fc_estate);
				fc_indexInfo->ii_PredicateState = fc_predicate;
			}

			/* 如果谓词不满足则跳过此索引更新 */
			if (!ExecQual(fc_predicate, fc_econtext))
				continue;
		}

		/*
		 * FormIndexDatum 用适当的值填充索引列的值和 isnull 参数.
		 */
		FormIndexDatum(fc_indexInfo,
					   fc_slot,
					   fc_estate,
					   fc_values,
					   fc_isnull);

		fc_satisfiesConstraint =
			fc_check_exclusion_or_unique_constraint(fc_heapRelation, fc_indexRelation,
												 fc_indexInfo, &fc_invalidItemPtr,
												 fc_values, fc_isnull, fc_estate, false,
												 CEOUC_WAIT, true,
												 fc_conflictTid);
		if (!fc_satisfiesConstraint)
			return false;
	}

	if (fc_arbiterIndexes != NIL && !fc_checkedIndex)
		elog(ERROR, "unexpected failure to find arbiter index");

	return true;
}

/*
 * 检查是否违反排除或唯一约束
 *
 * heap: 包含新元组的表
 * index: 支持约束的索引
 * indexInfo: 关于索引的信息，包括排除属性
 * tupleid: 我们刚插入的新元组的堆TID（如果我们
 *		尚未插入新元组，则无效）
 * values, isnull: 新元组计算出的*index*列值
 * estate: 一个我们可以进行评估的EState
 * newIndex: 如果为true，我们正在尝试构建一个新索引（这仅影响
 *		错误消息的措辞）
 * waitMode: 是否等待并发插入/删除者
 * violationOK: 如果为true，则不因违反抛出错误
 * conflictTid: 如果非NULL，冲突元组的TID将在此返回
 *
 * 如果OK则返回true，如果存在实际或潜在的违反则返回false
 *
 * 'waitMode'决定如果检测到与一个事务仍在运行的元组的冲突时发生什么。
 * CEOUC_WAIT意味着我们在抛出错误或返回之前等待事务提交。CEOUC_NOWAIT意味着我们立即报告
 * 违反; 因此，违反只是潜在的，调用者必须稍后重新检查。此行为方便了延迟的
 * 排除检查; 如果在插入时显然没有冲突，我们无需麻烦排队一个延迟事件。
 *
 * CEOUC_LIVELOCK_PREVENTING_WAIT类似于CEOUC_NOWAIT，但我们有时会
 * 等待，防止在两个事务同时尝试插入时发生活锁。这在投机性插入、INSERT ON
 * CONFLICT语句中使用。（请参见文件头中的注释）
 *
 * 如果violationOK为true，我们仅通过返回'false'来向调用者报告潜在或实际的违反。
 * 否则，我们会在这里抛出一个描述性的错误消息。当violationOK为false时，结果为false是
 * 不可能的。
 *
 * 注意：indexam通常负责检查唯一约束，
 * 因此这通常仅需要用于排除约束。但是在进行
 * 对唯一约束的冲突进行“预检查”时，此函数也会被调用，
 * 进行投机性插入。调用者可以使用返回的
 * 冲突TID来采取进一步措施。
 */
static bool fc_check_exclusion_or_unique_constraint(Relation fc_heap, Relation fc_index,
									 IndexInfo *fc_indexInfo,
									 ItemPointer fc_tupleid,
									 Datum *fc_values, bool *fc_isnull,
									 EState *fc_estate, bool fc_newIndex,
									 CEOUC_WAIT_MODE fc_waitMode,
									 bool fc_violationOK,
									 ItemPointer fc_conflictTid)
{
	Oid		   *fc_constr_procs;
	uint16	   *fc_constr_strats;
	Oid		   *fc_index_collations = fc_index->rd_indcollation;
	int			fc_indnkeyatts = IndexRelationGetNumberOfKeyAttributes(fc_index);
	IndexScanDesc fc_index_scan;
	ScanKeyData fc_scankeys[INDEX_MAX_KEYS];
	SnapshotData fc_DirtySnapshot;
	int			fc_i;
	bool		fc_conflict;
	bool		fc_found_self;
	ExprContext *fc_econtext;
	TupleTableSlot *fc_existing_slot;
	TupleTableSlot *fc_save_scantuple;

	if (fc_indexInfo->ii_ExclusionOps)
	{
		fc_constr_procs = fc_indexInfo->ii_ExclusionProcs;
		fc_constr_strats = fc_indexInfo->ii_ExclusionStrats;
	}
	else
	{
		fc_constr_procs = fc_indexInfo->ii_UniqueProcs;
		fc_constr_strats = fc_indexInfo->ii_UniqueStrats;
	}

	/*
	 * 如果任何输入值为NULL，并且索引使用默认
	 * nulls-are-distinct模式，则假定约束检查通过（即，
	 * 我们假定操作符是严格的）。否则，我们将约束解释为
	 * 指定每个输入值为NULL的列为IS NULL。
	 */
	if (!fc_indexInfo->ii_NullsNotDistinct)
	{
		for (fc_i = 0; fc_i < fc_indnkeyatts; fc_i++)
		{
			if (fc_isnull[fc_i])
				return true;
		}
	}

	/*
	 * 在索引中搜索存在的元组以查找任何违反，包括
	 * 尚不可见的元组。
	 */
	InitDirtySnapshot(fc_DirtySnapshot);

	for (fc_i = 0; fc_i < fc_indnkeyatts; fc_i++)
	{
		ScanKeyEntryInitialize(&fc_scankeys[fc_i],
							   fc_isnull[fc_i] ? SK_ISNULL | SK_SEARCHNULL : 0,
							   fc_i + 1,
							   fc_constr_strats[fc_i],
							   InvalidOid,
							   fc_index_collations[fc_i],
							   fc_constr_procs[fc_i],
							   fc_values[fc_i]);
	}

	/*
	 * 需要一个TupleTableSlot来放置现有元组。
	 *
	 * 要使用FormIndexDatum，我们必须使econtext的scantuple指向
	 * 此插槽。务必保存并恢复调用者的
	 * scantuple值。
	 */
	fc_existing_slot = table_slot_create(fc_heap, NULL);

	fc_econtext = GetPerTupleExprContext(fc_estate);
	fc_save_scantuple = fc_econtext->ecxt_scantuple;
	fc_econtext->ecxt_scantuple = fc_existing_slot;

	/*
	 * 如果发现潜在冲突，可能需要从此点重新开始扫描。
	 */
retry:
	fc_conflict = false;
	fc_found_self = false;
	fc_index_scan = index_beginscan(fc_heap, fc_index, &fc_DirtySnapshot, fc_indnkeyatts, 0);
	index_rescan(fc_index_scan, fc_scankeys, fc_indnkeyatts, NULL, 0);

	while (index_getnext_slot(fc_index_scan, ForwardScanDirection, fc_existing_slot))
	{
		TransactionId fc_xwait;
		XLTW_Oper	fc_reason_wait;
		Datum		fc_existing_values[INDEX_MAX_KEYS];
		bool		fc_existing_isnull[INDEX_MAX_KEYS];
		char	   *fc_error_new;
		char	   *fc_error_existing;

		/*
		 * 忽略我们试图检查的元组的条目。
		 */
		if (ItemPointerIsValid(fc_tupleid) &&
			ItemPointerEquals(fc_tupleid, &fc_existing_slot->tts_tid))
		{
			if (fc_found_self)		/* 不应该发生 */
				elog(ERROR, "found self tuple multiple times in index \"%s\"",
					 RelationGetRelationName(fc_index));
			fc_found_self = true;
			continue;
		}

		/*
		 * 从现有元组中提取索引列值和isnull标志。
		 */
		FormIndexDatum(fc_indexInfo, fc_existing_slot, fc_estate,
					   fc_existing_values, fc_existing_isnull);

		/* 如果是有损的索引扫描，必须重新检查条件 */
		if (fc_index_scan->xs_recheck)
		{
			if (!fc_index_recheck_constraint(fc_index,
										  fc_constr_procs,
										  fc_existing_values,
										  fc_existing_isnull,
										  fc_values))
				continue;		/* 元组实际上不匹配，所以没有冲突 */
		}

		/*
		 * 在这一点上，我们有冲突或潜在的冲突。
		 *
		 * 如果一个正在进行的事务影响到该元组的可见性，我们需要等待它完成，然后重新检查（除非调用者请求不这样做）。为了简单起见，我们通过重新启动整个扫描来进行重新检查——这种情况可能不会经常发生，因此不值得更努力地尝试，而且在等待期间我们也不想保持任何索引内部锁。
		 */
		fc_xwait = TransactionIdIsValid(fc_DirtySnapshot.xmin) ?
			fc_DirtySnapshot.xmin : fc_DirtySnapshot.xmax;

		if (TransactionIdIsValid(fc_xwait) &&
			(fc_waitMode == CEOUC_WAIT ||
			 (fc_waitMode == CEOUC_LIVELOCK_PREVENTING_WAIT &&
			  fc_DirtySnapshot.speculativeToken &&
			  TransactionIdPrecedes(GetCurrentTransactionId(), fc_xwait))))
		{
			fc_reason_wait = fc_indexInfo->ii_ExclusionOps ?
				XLTW_RecheckExclusionConstr : XLTW_InsertIndex;
			index_endscan(fc_index_scan);
			if (fc_DirtySnapshot.speculativeToken)
				SpeculativeInsertionWait(fc_DirtySnapshot.xmin,
										 fc_DirtySnapshot.speculativeToken);
			else
				XactLockTableWait(fc_xwait, fc_heap,
								  &fc_existing_slot->tts_tid, fc_reason_wait);
			goto retry;
		}

		/*
		 * 我们有一个明确的冲突（或者是潜在的，但调用者不想等待）。返回给调用者，或者报告它。
		 */
		if (fc_violationOK)
		{
			fc_conflict = true;
			if (fc_conflictTid)
				*fc_conflictTid = fc_existing_slot->tts_tid;
			break;
		}

		fc_error_new = BuildIndexValueDescription(fc_index, fc_values, fc_isnull);
		fc_error_existing = BuildIndexValueDescription(fc_index, fc_existing_values,
													fc_existing_isnull);
		if (fc_newIndex)
			ereport(ERROR,
					(errcode(ERRCODE_EXCLUSION_VIOLATION),
					 errmsg("could not create exclusion constraint \"%s\"",
							RelationGetRelationName(fc_index)),
					 fc_error_new && fc_error_existing ?
					 errdetail("Key %s conflicts with key %s.",
							   fc_error_new, fc_error_existing) :
					 errdetail("Key conflicts exist."),
					 errtableconstraint(fc_heap,
										RelationGetRelationName(fc_index))));
		else
			ereport(ERROR,
					(errcode(ERRCODE_EXCLUSION_VIOLATION),
					 errmsg("conflicting key value violates exclusion constraint \"%s\"",
							RelationGetRelationName(fc_index)),
					 fc_error_new && fc_error_existing ?
					 errdetail("Key %s conflicts with existing key %s.",
							   fc_error_new, fc_error_existing) :
					 errdetail("Key conflicts with existing key."),
					 errtableconstraint(fc_heap,
										RelationGetRelationName(fc_index))));
	}

	index_endscan(fc_index_scan);

	/*
	 * 通常，在这一点上搜索应该找到了最初插入的元组（如果有的话），除非由于冲突我们提前退出了循环。然而，有可能定义排除约束，这种情况不成立——例如，如果运算符是<>。所以如果 found_self 仍然为假，我们不再抱怨。
	 */

	fc_econtext->ecxt_scantuple = fc_save_scantuple;

	ExecDropSingleTupleTableSlot(fc_existing_slot);

	return !fc_conflict;
}

/*
 * 检查排除约束的违规行为
 *
 * 这是为外部调用者简化版的 check_exclusion_or_unique_constraint。他们不需要所有特殊模式。
 */
void check_exclusion_constraint(Relation fc_heap, Relation fc_index,
						   IndexInfo *fc_indexInfo,
						   ItemPointer fc_tupleid,
						   Datum *fc_values, bool *fc_isnull,
						   EState *fc_estate, bool fc_newIndex)
{
	(void) fc_check_exclusion_or_unique_constraint(fc_heap, fc_index, fc_indexInfo, fc_tupleid,
												fc_values, fc_isnull,
												fc_estate, fc_newIndex,
												CEOUC_WAIT, false, NULL);
}

/*
 * 检查现有元组的索引值，以查看它是否真正与 new_values 的排除条件匹配。如果冲突则返回 true。
 */
static bool fc_index_recheck_constraint(Relation fc_index, Oid *fc_constr_procs,
						 Datum *fc_existing_values, bool *fc_existing_isnull,
						 Datum *fc_new_values)
{
	int			fc_indnkeyatts = IndexRelationGetNumberOfKeyAttributes(fc_index);
	int			fc_i;

	for (fc_i = 0; fc_i < fc_indnkeyatts; fc_i++)
	{
		/* 假设排除运算符是严格的 */
		if (fc_existing_isnull[fc_i])
			return false;

		if (!DatumGetBool(OidFunctionCall2Coll(fc_constr_procs[fc_i],
											   fc_index->rd_indcollation[fc_i],
											   fc_existing_values[fc_i],
											   fc_new_values[fc_i])))
			return false;
	}

	return true;
}

/*
 * 检查 ExecInsertIndexTuples() 是否应该传递 indexUnchanged 提示。
 *
 * 当执行器执行需要新的索引元组的 UPDATE 时，确定是否应该为单个索引传递 'indexUnchanged' = true 提示。
 */
static bool fc_index_unchanged_by_update(ResultRelInfo *fc_resultRelInfo, EState *fc_estate,
						  IndexInfo *fc_indexInfo, Relation fc_indexRelation)
{
	Bitmapset  *fc_updatedCols;
	Bitmapset  *fc_extraUpdatedCols;
	Bitmapset  *fc_allUpdatedCols;
	bool		fc_hasexpression = false;
	List	   *fc_idxExprs;

	/*
	 * 首先检查缓存
	 */
	if (fc_indexInfo->ii_CheckedUnchanged)
		return fc_indexInfo->ii_IndexUnchanged;
	fc_indexInfo->ii_CheckedUnchanged = true;

	/*
	 * 检查索引属性与更新列的重叠。
	 *
	 * 仅对关键列进行此操作。对 INCLUDE 索引中的非关键列的更改不应在此处计算。非关键列值对索引 AM 是不透明的有效载荷状态，有点类似于额外的表 TID。
	 *
	 * 请注意，行级 BEFORE 触发器不会影响我们的行为，因为它们通常不会影响 updatedCols 位图。检查哪些属性被直接更改似乎没有太大意义。
	 */
	fc_updatedCols = ExecGetUpdatedCols(fc_resultRelInfo, fc_estate);
	fc_extraUpdatedCols = ExecGetExtraUpdatedCols(fc_resultRelInfo, fc_estate);
	for (int fc_attr = 0; fc_attr < fc_indexInfo->ii_NumIndexKeyAttrs; fc_attr++)
	{
		int			fc_keycol = fc_indexInfo->ii_IndexAttrNumbers[fc_attr];

		if (fc_keycol <= 0)
		{
			/*
			 * 现在跳过表达式，但记得稍后处理它们
			 */
			fc_hasexpression = true;
			continue;
		}

		if (bms_is_member(fc_keycol - FirstLowInvalidHeapAttributeNumber,
						  fc_updatedCols) ||
			bms_is_member(fc_keycol - FirstLowInvalidHeapAttributeNumber,
						  fc_extraUpdatedCols))
		{
			/* 更改了关键列——对于此索引不提示 */
			fc_indexInfo->ii_IndexUnchanged = false;
			return false;
		}
	}

	/*
	 * 当我们走到这一步且索引没有表达式时，返回 true，以便 index_insert() 调用将继续传递 'indexUnchanged' = true 提示。
	 *
	 * 缺少与更新属性重叠的索引关键属性（除了索引表达式的完全缺失）表明索引在逻辑上未被 UPDATE 更改。
	 */
	if (!fc_hasexpression)
	{
		fc_indexInfo->ii_IndexUnchanged = true;
		return true;
	}

	/*
	 * 仅需要将一个 bms 传递给 expression_tree_walker 辅助函数。
	 * 避免在没有额外列的常见情况下分配内存。
	 */
	if (!fc_extraUpdatedCols)
		fc_allUpdatedCols = fc_updatedCols;
	else
		fc_allUpdatedCols = bms_union(fc_updatedCols, fc_extraUpdatedCols);

	/*
	 * 在有索引表达式的情况下，我们必须稍微更加努力，但原则与之前相同：尝试找到与已知更新列重叠的列（实际上是 Vars）。
	 *
	 * 如果找到任何匹配的 Vars，则不传递提示给索引。否则传递提示。
	 */
	fc_idxExprs = RelationGetIndexExpressions(fc_indexRelation);
	fc_hasexpression = fc_index_expression_changed_walker((Node *) fc_idxExprs,
													fc_allUpdatedCols);
	list_free(fc_idxExprs);
	if (fc_extraUpdatedCols)
		bms_free(fc_allUpdatedCols);

	if (fc_hasexpression)
	{
		fc_indexInfo->ii_IndexUnchanged = false;
		return false;
	}

	/*
	 * 刻意不考虑索引谓词。即使结果关系的“更新元组”没有对应的索引元组，当满足通常条件时，这种情况也是可能的，所以我们应该提供提示。
	 */
	fc_indexInfo->ii_IndexUnchanged = true;
	return true;
}

/*
 * 用于 index_unchanged_by_update() 的索引表达式助手。
 *
 * 当在所有已更新列中找到 Var 时返回 true。
 */
static bool fc_index_expression_changed_walker(Node *fc_node, Bitmapset *fc_allUpdatedCols)
{
	if (fc_node == NULL)
		return false;

	if (IsA(fc_node, Var))
	{
		Var		   *fc_var = (Var *) fc_node;

		if (bms_is_member(fc_var->varattno - FirstLowInvalidHeapAttributeNumber,
						  fc_allUpdatedCols))
		{
			/* Var 被更新——表示我们不应该提示 */
			return true;
		}

		/* 仍然没有找到不传递提示的理由 */
		return false;
	}

	return expression_tree_walker(fc_node, fc_index_expression_changed_walker,
								  (void *) fc_allUpdatedCols);
}
